package helper.scxml.scxml2.t0.case1_cycle.zone

import helper.base.BaseTypeHelper.toIntRange
import helper.base.LHMHelper.A2LHMExpand.add
import helper.base.LHMHelper.A2LHMExpand.addIfAbsent
import helper.base.LHMHelper.A2LHMExpand.makeA2LHM
import helper.base.LHMHelper.A3LHM.Companion.makeA3LHM
import helper.base.TupleHelper.GetTuple.getA2Tuple
import helper.base.ZoneConstraintHelper.CoeffVar.Companion.makeCoeffVar
import helper.base.ZoneConstraintHelper.Constraint
import helper.base.ZoneConstraintHelper.PointConstraint.Companion.makePointConstraint
import helper.base.ZoneConstraintHelper.ZoneConstraint
import helper.base.ZoneConstraintHelper.ZoneConstraint.Companion.makePointZoneConstraint
import helper.base.ZoneConstraintHelper.ZoneConstraint.Companion.makeZoneConstraint
import helper.base.debug.DebugHelper.DebuggerList
import helper.scxml.scxml2.t0.case1_cycle.zone.CycleEnvHelper.T2SNodeUnitObj.getBestRenStrategy
import helper.scxml.scxml2.t0.case1_cycle.zone.CycleEnvHelper.T2SNodeUnitObj.getE1Strategy
import helper.scxml.scxml2.t0.case1_cycle.zone.CycleEnvHelper.T2SNodeUnitObj.getE2Strategy
import helper.scxml.scxml2.t0.case1_cycle.zone.CycleEnvHelper.T2SNodeUnitObj.getR0Strategy
import helper.scxml.scxml2.zone.t2.ConfigHelper.Config
import helper.scxml.scxml2.zone.t2.ConfigHelper.Config.Companion.makeConfig
import helper.scxml.scxml2.zone.t2.NoteRunResultFun
import helper.scxml.scxml2.zone.t2.ZoneEnvHelper.ConstraintGetter.Companion.makeConstraintGetter
import helper.scxml.scxml2.zone.t2.ZoneEnvHelper.ConstraintGetterArg.Companion.makeConstraintGetterArg
import helper.scxml.scxml2.zone.t2.ZoneEnvHelper.Edge.Companion.makeEdge
import helper.scxml.scxml2.zone.t2.ZoneEnvHelper.IStrategyEx
import helper.scxml.scxml2.zone.t2.ZoneEnvHelper.ZoneEnv
import helper.scxml.scxml2.zone.t2.ZoneEnvHelper.equal_name_StrStrBoolFun
import helper.scxml.scxml2.zone.t2.ZoneEnvHelper.txNoteRunResultFun
import helper.scxml.strategy.StrategyHelper.TimedEventHelper.TimedEvent
import helper.scxml.strategy.TotalStrategyHelper.KREWTuple.Companion.makeKREWTuple
import helper.scxml.strategy.TotalStrategyHelper.SNode.Companion.makeSNode
import helper.scxml.strategy.sNodeUnit.T1SNodeUnitHelper.T1SNodeUnit
import helper.scxml.strategy.sNodeUnit.T1SNodeUnitHelper.T1SNodeUnit.Companion.newT1SNodeUnit
import helper.scxml.strategy.sNodeUnit.T1SNodeUnitHelper.addToT1SNodeUnit
import helper.scxml.strategy.sNodeUnit.T2SNodeUnitHelper.ERT2SNodeUnit.Companion.makeL0ERT2SNodeUnit
import helper.scxml.strategy.sNodeUnit.T2SNodeUnitHelper.SubT2SNodeUnit
import helper.scxml.strategy.sNodeUnit.T2SNodeUnitHelper.SubT2SNodeUnit.Companion.makeSubT2SNodeUnit
import helper.scxml.strategy.sNodeUnit.T2SNodeUnitHelper.T2SNodeUnit

object CycleEnvHelper {
    class CycleEnv : ZoneEnv() {
        companion object {
            fun makeCycleEnv() = CycleEnv()
        }

        override val timeNeedEnd: Int = 340

        val finalLocationList: ArrayList<String> by lazy {
            arrayListOf("s4")
        }

        override fun isOnFinalLocation(nowZS: Config): Boolean {
            return finalLocationList.contains(nowZS.getState0String())
        }

        override fun isEnd(nowZS: Config, nextMinEventTime: Int): Boolean {
            return isOnFinalLocation(nowZS)
        }

        override val stateVarRateA3LHM by lazy {
            makeA3LHM<String, String, Int>().also { lhm ->
                arrayOf(
                    getA2Tuple("s0", 0),
                    getA2Tuple("s1", 4),
                    getA2Tuple("s2", 3),
                    getA2Tuple("s3", 2),
                    getA2Tuple("s4", 0),
                ).forEach {
                    lhm.addIfAbsent(it.first, "c", it.second)
                }
            }
        }

        private fun getLCRate(location: String): Int {
            return stateVarRateA3LHM[location, "c"]!!
        }

        override val aStateEventStateLHM by lazy {
            makeA3LHM<String, String, String>().also { lhm ->
                arrayOf(
                    makeEdge("s0", "s1"),
                    makeEdge("s1", "s2"),
                    makeEdge("s1", "s3"),
                    makeEdge("s1", "s4", "s1s4t1"),
                    makeEdge("s1", "s4", "s1s4t2"),
                    makeEdge("s2", "s4"),
                    makeEdge("s3", "s4"),
                ).forEach {
                    lhm.addIfAbsent(
                        it.locationStart,
                        it.edgeName,
                        it.locationEnd,
                    )
                }
            }
        }

        override val stateStayMaxLHM by lazy {
            makeA2LHM<String, Int>().also { lhm ->
                arrayOf(
                    getA2Tuple("s0", 100),
                    getA2Tuple("s1", 100),
                    getA2Tuple("s2", 120),
                    getA2Tuple("s3", 140),
                    getA2Tuple("s4", 0)
                ).forEach {
                    lhm.addIfAbsent(it.first, it.second)
                }
            }
        }

        val tConstraintGetter by lazy {
            makeConstraintGetter(
                "t",
                equal_name_StrStrBoolFun,
            ) { (oldZS, oldConstraint, timeEventChoiceArr) ->
                val time = timeEventChoiceArr[0].time
                val d1 = oldConstraint.getPointD() + time
                require(oldConstraint is ZoneConstraint)
                makeZoneConstraint(
                    oldConstraint.cloneCVs(),
                    oldConstraint.getIfEq1(),
                    d1,
                    d1,
                    oldConstraint.getIfEq2(),
                )
            }
        }

        val cConstraintGetter by lazy {
            makeConstraintGetter(
                "c",
                equal_name_StrStrBoolFun,
            ) { (oldLs, oldConstraint, timeEventChoiceArr) ->
                val oldLocation = oldLs.first()
                val (time, event) = timeEventChoiceArr[0]
                val d1 = oldConstraint.getPointD() + getLCRate(oldLocation) * time
                makePointConstraint(
                    "c",
                    d1,
                )
            }
        }

        override fun updateConstraint(
            exitStateList:ArrayList<String>,
            oldConfig: Config,
            newConfig: Config,
            timedEventList: List<TimedEvent>,
        ) {
            val newCs = ArrayList<Constraint>()
            listOf(
                tConstraintGetter,
                cConstraintGetter,
            ).forEach {
                val newC: Constraint = it.getNewConstraint(
                    makeConstraintGetterArg(
                        oldConfig.states,
                        newConfig,
                        timedEventList,
                        newConfig.states,
                    )
                )
                newCs.add(newC)
            }
            newCs.forEach {
                newConfig.varConstraintLHM[it.getVariablesString()] = it
            }
        }

        override fun getInitialConfig(debuggerList: DebuggerList): Config {
            return makeConfig(
                "s0",
                makeZoneConstraint(
                    arrayListOf(
                        makeCoeffVar(1.0, "t"),
                    ),
                    true,
                    0.0,
                    100.0,
                    true,
                ),
                makePointZoneConstraint(
                    arrayListOf(
                        makeCoeffVar(1.0, "c"),
                    ),
                    0.0,
                ),
            )
        }

        override val aStateNoteRunResultFunLHM by lazy {
            makeA2LHM<String, NoteRunResultFun>().also { lhm ->
                lhm["s1"] = txNoteRunResultFun
            }
        }

        override val allVars: List<String> by lazy {
            listOf(tClock, "c")
        }
    }

    object T1SNodeUnitObj {
        fun getT1T1SNodeUnit(): T1SNodeUnit {
            val rootSNode = makeSNode()
            val sNodeUnit = newT1SNodeUnit(rootSNode)
            sNodeUnit.funLHM[rootSNode] = Config::getState0String
            arrayOf(
                makeKREWTuple("s0", 50.toIntRange(), "s0s1", 1.0),
                makeKREWTuple("s1", 60.toIntRange(), "s1s2", 1.0),
                makeKREWTuple("s2", 70.toIntRange(), "s2s4", 1.0),
            ).addToT1SNodeUnit(
                sNodeUnit,
                rootSNode,
            )
            return sNodeUnit
        }

        fun getT2T1SNodeUnit(): T1SNodeUnit {
            val rootSNode = makeSNode()
            val sNodeUnit = newT1SNodeUnit(rootSNode)
            sNodeUnit.funLHM[rootSNode] = Config::getState0String
            arrayOf(
                makeKREWTuple("s0", 0..100, "s0s1", 1),
                makeKREWTuple("s2", 60..120, "s2s4", 1),
                makeKREWTuple("s3", 20..140, "s3s4", 1),
                makeKREWTuple("s1", 0 until 90, "s1s2", (90 / 101.0) / 2),
                makeKREWTuple("s1", 0 until 90, "s1s3", (90 / 101.0) / 2),
                makeKREWTuple("s1", 90 until 100, "s1s2", (10 / 101.0) / 3),
                makeKREWTuple("s1", 90 until 100, "s1s3", (10 / 101.0) / 3),
                makeKREWTuple("s1", 90 until 100, "s1s4t2", (10 / 101.0) / 3),
                makeKREWTuple("s1", 100..100, "s1s4t1", 1 / 101.0),
            ).addToT1SNodeUnit(
                sNodeUnit,
                rootSNode,
            )
            return sNodeUnit
        }

        fun getT3T1SNodeUnit(): T1SNodeUnit {
            val rootSNode = makeSNode()
            val sNodeUnit = newT1SNodeUnit(rootSNode)
            sNodeUnit.funLHM[rootSNode] = Config::getState0String
            arrayOf(
                makeKREWTuple("s0", 0..100, "s0s1", 1),
                makeKREWTuple("s2", 60..120, "s2s4", 1),
                makeKREWTuple("s3", 20..140, "s3s4", 1),
            ).addToT1SNodeUnit(
                sNodeUnit,
                rootSNode,
            )
            val s1SNode = makeSNode()
            sNodeUnit.relationLHM.addIfAbsent(rootSNode, "s1", s1SNode)
            sNodeUnit.funLHM[s1SNode] = {
                val enterS1Time = it.byNameFindC("t").getPointD()
                if (enterS1Time <= 70) {
                    "t<=70"
                } else if (enterS1Time <= 90) {
                    "70<t<=90"
                } else if (enterS1Time > 90) {
                    "t>90"
                } else {
                    ""
                }
            }
            arrayOf(
                makeKREWTuple("t<=70", 0 until 90, "s1s3", 90 / 101.0),
                makeKREWTuple("t<=70", 90 until 100, "s1s3", 5 / 101.0),
                makeKREWTuple("t<=70", 90 until 100, "s1s4t2", 5 / 101.0),
                makeKREWTuple("t<=70", 100.toIntRange(), "s1s4t1", 1 / 101.0),
                makeKREWTuple("70<t<=90", 0 until 90, "s1s2", 90 / 101.0),
                makeKREWTuple("70<t<=90", 90 until 100, "s1s2", 5 / 101.0),
                makeKREWTuple("70<t<=90", 90 until 100, "s1s4t2", 5 / 101.0),
                makeKREWTuple("70<t<=90", 100.toIntRange(), "s1s4t1", 1 / 101.0),
                makeKREWTuple("t>90", 100.toIntRange(), "s1s4t1", 1),
            ).addToT1SNodeUnit(
                sNodeUnit,
                s1SNode,
            )
            return sNodeUnit
        }

        fun getT4T1SNodeUnit(): T1SNodeUnit {
            val rootSNode = makeSNode()
            val sNodeUnit = newT1SNodeUnit(rootSNode)
            sNodeUnit.funLHM[rootSNode] = Config::getState0String
            arrayOf(
                makeKREWTuple("s0", 0..100, "s0s1", 1),
                makeKREWTuple("s1", 0.toIntRange(), "s1s3", 1),
                makeKREWTuple("s2", 60..120, "s2s4", 1),
                makeKREWTuple("s3", 20..140, "s3s4", 1),
            ).addToT1SNodeUnit(
                sNodeUnit,
                rootSNode,
            )
            return sNodeUnit
        }

        fun getT5T1SNodeUnit(): T1SNodeUnit {
            val rootSNode = makeSNode()
            val sNodeUnit = newT1SNodeUnit(rootSNode)
            sNodeUnit.funLHM[rootSNode] = Config::getState0String
            arrayOf(
                makeKREWTuple("s0", 0..100, "s0s1", 1),
                makeKREWTuple("s1", 90.toIntRange(), "s1s2", 1),
                makeKREWTuple("s2", 60..120, "s2s4", 1),
                makeKREWTuple("s3", 20..140, "s3s4", 1),
            ).addToT1SNodeUnit(
                sNodeUnit,
                rootSNode,
            )
            return sNodeUnit
        }
    }

    object T2SNodeUnitObj {
        fun getE1Strategy(): SubT2SNodeUnit {
            return makeSubT2SNodeUnit().also {
                it.addSNode(
                    makeKREWTuple("s0", 0..100, "s0s1", 1),
                    makeKREWTuple("s2", 60..120, "s2s4", 1),
                    makeKREWTuple("s3", 20..140, "s3s4", 1),
                )
            }
        }

        fun getE2Strategy(): SubT2SNodeUnit {
            return makeSubT2SNodeUnit().also {
                it.addSNode(
                    makeKREWTuple("s0", 0..0, "s0s1", 1),
                    makeKREWTuple("s2", 60..120, "s2s4", 1),
                    makeKREWTuple("s3", 20..140, "s3s4", 1),
                )
            }
        }

        //均匀例子
        fun getR0Strategy(): SubT2SNodeUnit {
            return makeSubT2SNodeUnit().also {
                it.addSNode(
                    makeKREWTuple("s1", 100..100, "s1s4t1", 1 / 101.0),
                    makeKREWTuple("s1", 0 until 90, "s1s2", (90 / 101.0) / 2),
                    makeKREWTuple("s1", 0 until 90, "s1s3", (90 / 101.0) / 2),
                    makeKREWTuple("s1", 90 until 100, "s1s2", (10 / 101.0) / 3),
                    makeKREWTuple("s1", 90 until 100, "s1s3", (10 / 101.0) / 3),
                    makeKREWTuple("s1", 90 until 100, "s1s4t2", (10 / 101.0) / 3),
                )
            }
        }

        //实例1
        fun getT1T2SNodeUnit(): T2SNodeUnit {
            return makeL0ERT2SNodeUnit(
                env = makeSubT2SNodeUnit().also {
                    it.addSNode(
                        makeKREWTuple("s0", 50.toIntRange(), "s0s1", 1.0),
                        makeKREWTuple("s2", 70.toIntRange(), "s2s4", 1.0),
                    )
                },
                ren = makeSubT2SNodeUnit().also {
                    it.addSNode(
                        makeKREWTuple("s1", 60.toIntRange(), "s1s2", 1.0),
                    )
                },
            )
        }

        //实例2
        fun getE1R1Strategy(): T2SNodeUnit {
            return makeL0ERT2SNodeUnit(
                env = getE1Strategy(),
                ren = getR0Strategy(),
            )
        }

        //实例3
        fun getT3T2SNodeUnit(): T2SNodeUnit {
            return makeL0ERT2SNodeUnit(
                env = getE1Strategy(),
                ren = makeSubT2SNodeUnit().apply {
                    relationLHM.add(
                        null,
                        "s1",
                        makeSNode().also { aSNode ->
                            funLHM.add(aSNode) {
                                val enterS1Time = it.byNameFindC("t").getPointD()
                                if (enterS1Time <= 70) {
                                    "t<=70"
                                } else if (enterS1Time > 70 && enterS1Time <= 90) {
                                    "70<t<=90"
                                } else {
                                    "t>90"
                                }
                            }
                            arrayOf(
                                makeKREWTuple("t<=70", 0 until 90, "s1s3", 90 / 101.0),
                                makeKREWTuple("t<=70", 90 until 100, "s1s3", 5 / 101.0),
                                makeKREWTuple("t<=70", 90 until 100, "s1s4t2", 5 / 101.0),
                                makeKREWTuple("t<=70", 100.toIntRange(), "s1s4t1", 1 / 101.0),
                                makeKREWTuple("70<t<=90", 0 until 90, "s1s2", 90 / 101.0),
                                makeKREWTuple("70<t<=90", 90 until 100, "s1s2", 5 / 101.0),
                                makeKREWTuple("70<t<=90", 90 until 100, "s1s4t2", 5 / 101.0),
                                makeKREWTuple("70<t<=90", 100.toIntRange(), "s1s4t1", 1 / 101.0),
                                makeKREWTuple("t>90", 100.toIntRange(), "s1s4t1", 1),
                            ).forEach {
                                this.addSNode(aSNode, it)
                            }
                        },
                    )
                },
            )
        }

        fun getBestRenStrategy() = makeSubT2SNodeUnit().also {
            it.addSNode(
                makeKREWTuple("s1", 0.toIntRange(), "s1s3", 1),
            )
        }

        //实例4
        //best
        fun getCycleBestT2SNodeUnit(env: SubT2SNodeUnit = getE1Strategy()): T2SNodeUnit {
            return makeL0ERT2SNodeUnit(
                env,
                getBestRenStrategy(),
            )
        }

        //实例5
        fun getT5T2SNodeUnit(): T2SNodeUnit {
            return makeL0ERT2SNodeUnit(
                getE1Strategy(),
                makeSubT2SNodeUnit().also {
                    it.addSNode(
                        makeKREWTuple("s1", 90.toIntRange(), "s1s2", 1),
                    )
                },
            )
        }
    }

    open class BCycleStrategyEx(
        val aEnvFun: () -> SubT2SNodeUnit,
        val aFixedRenFun: () -> SubT2SNodeUnit,
    ) : IStrategyEx<T2SNodeUnit> {
        fun makeStrategy(ren: SubT2SNodeUnit): T2SNodeUnit {
            return makeL0ERT2SNodeUnit(aEnvFun(), ren)
        }

        override fun makeNewStrategy(): T2SNodeUnit {
            return makeStrategy(ren = makeSubT2SNodeUnit())
        }

        override fun makeFixedStrategy(): T2SNodeUnit {
            return makeStrategy(ren = aFixedRenFun())
        }
    }

    val aE1UniformCycleStrategyEx = BCycleStrategyEx(
        { getE1Strategy() },
        { getR0Strategy() },
    )

    val aE2UniformCycleStrategyEx = BCycleStrategyEx(
        { getE2Strategy() },
        { getR0Strategy() },
    )

    val aE2BestCycleStrategyEx = BCycleStrategyEx(
        { getE2Strategy() },
        { getBestRenStrategy() },
    )
}